package com.dwm716.shorturl.service.impl;

import com.dwm716.shorturl.service.ShortUrlService;
import com.dwm716.shorturl.service.UrlMapService;
import com.dwm716.shorturl.utils.*;
import com.google.common.collect.Lists;
import com.google.common.hash.Hashing;
import com.dwm716.shorturl.common.ApiResponse;
import com.dwm716.shorturl.domain.RequestRecord;
import com.dwm716.shorturl.domain.UrlMap;
import com.dwm716.shorturl.dto.CreateShortUrlVo;
import com.dwm716.shorturl.dto.ShortUrlQuery;
import com.dwm716.shorturl.pagehelper.PageResult;
import com.dwm716.shorturl.service.RequestRecordService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.RandomStringUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.web.servlet.view.RedirectView;

import javax.servlet.http.HttpServletRequest;
import java.util.List;
import java.util.Objects;

/**
 * @author biliyu
 * @date 2023/6/6 10:48
 */
@Slf4j
@Service
public class ShortUrlServiceImpl implements ShortUrlService {

    @Value("${huaixia.url}")
    private String shortUrl;

    @Autowired
    private RedisClientUtil redisClientUtil;

    @Autowired
    private BloomFilterUtil bloomFilterUtil;

    @Autowired
    private UrlMapService urlMapService;

    @Autowired
    private RequestRecordService requestRecordService;

    /**
     * 创建短链接
     *
     * @param createShortUrlVo 创建短链接的请求对象
     * @return ApiResponse<String> 包含创建短链接结果的响应对象
     */
    @Override
    public ApiResponse<UrlMap> createShortUrl(CreateShortUrlVo createShortUrlVo) {
        if (Objects.isNull(createShortUrlVo.getUserId())) {
            throw new RuntimeException("非用户");
        }
        // 从请求对象中获取原始 URL
        String url = createShortUrlVo.getUrl();
        url = UrlUtil.appendKey(url);

        // 从请求对象中获取自定义短链接名称
        String name = createShortUrlVo.getName();
        // 从请求对象中获取备注信息
        String remark = createShortUrlVo.getRemark();

        // 使用 MurmurHash3 算法对原始 URL 进行哈希计算，得到一个 32 位的整数
        long hash = Hashing.murmur3_32_fixed().hashUnencodedChars(url).padToLong();
        // 将十进制整数转换为 62 进制字符串
        String base62 = DecimalToBase62Util.decimalToBase62(hash);
        // 检查 62 进制字符串是否已存在，如果存在则生成新的 62 进制字符串
        base62 = checkBase62(url, base62);

        // 创建一个 UrlMap 对象，用于存储短链接和原始链接的映射关系
        UrlMap urlMap = new UrlMap();
        // 设置短链接
        urlMap.setShortUrl(base62);
        // 设置原始链接
        urlMap.setLongUrl(url);
        // 设置自定义短链接名称
        urlMap.setName(name);
        // 设置备注信息
        urlMap.setRemark(remark);

        urlMap.setUserId(createShortUrlVo.getUserId());

        urlMap.setStatus(UrlMapStatus.Init.name());

        // 将 UrlMap 对象插入数据库
        int insert = urlMapService.insert(urlMap);
        // 如果插入成功
        if (insert > 0) {
            // 向布隆过滤器中添加短链接
            bloomFilterUtil.bfreserve(base62, 0.01, 10000);
            bloomFilterUtil.bfadd(base62, url);
            // 将原始链接存储到 Redis 中，以短链接作为键
            redisClientUtil.set(base62, url);
            urlMap.setShortUrl(shortUrl + base62);
            urlMap.setLongUrl(UrlUtil.subKey(urlMap.getLongUrl()));
            return ApiResponse.ok(urlMap);
        }
        // 如果插入失败，返回一个空的成功响应
        return ApiResponse.ok();
    }



    /**
     * 根据短链接的key进行重定向
     *
     * @param request HttpServletRequest对象
     * @param key 短链接的唯一标识符
     * @return RedirectView 重定向视图
     */
    @Override
    public RedirectView redirect(HttpServletRequest request, String key) {

        // 获取 IP 地址
        String ipAddress = request.getHeader("X-Forwarded-For");

        if (ipAddress == null || ipAddress.isEmpty() || "unknown".equalsIgnoreCase(ipAddress)) {
            ipAddress = request.getHeader("Proxy-Client-IP");
        }

        if (ipAddress == null || ipAddress.isEmpty() || "unknown".equalsIgnoreCase(ipAddress)) {
            ipAddress = request.getHeader("WL-Proxy-Client-IP");
        }

        if (ipAddress == null || ipAddress.isEmpty() || "unknown".equalsIgnoreCase(ipAddress)) {
            ipAddress = request.getHeader("HTTP_CLIENT_IP");
        }

        if (ipAddress == null || ipAddress.isEmpty() || "unknown".equalsIgnoreCase(ipAddress)) {
            ipAddress = request.getHeader("HTTP_X_FORWARDED_FOR");
        }

        if (ipAddress == null || ipAddress.isEmpty() || "unknown".equalsIgnoreCase(ipAddress)) {
            ipAddress = request.getRemoteAddr();
        }

        // 处理多级代理的情况，只取第一个IP
        if (ipAddress!= null && ipAddress.contains(",")) {
            ipAddress = ipAddress.split(",")[0];
        }

        // 使用 IpShuDiUtil 类获取 IP 地址对应的地理位置信息
//        JSONObject ipToAddress = IpShuDiUtil.getIpToAddress(ipAddress);
//        log.info(String.valueOf(ipToAddress));

        // 根据短链接的 key 从数据库中获取对应的原始链接
        UrlMap urlMap = urlMapService.getByShortUrl(key);

        // 如果没有找到对应的原始链接，则返回一个空的重定向视图
        if (urlMap == null) {
            return new RedirectView();
        }

        // 创建一个请求记录对象，用于记录本次请求的相关信息
//        RequestRecord requestRecord = new RequestRecord();
//        requestRecord.setUrlId(urlMap.getId());
//        requestRecord.setIpAddress(ipAddress);

        // 从 IP 地址对应的地理位置信息中获取滥用信息
//        JSONObject address = ipToAddress.getJSONObject("address");
//        JSONObject data = address.getJSONObject("data");
//        if (data!= null) {
//            JSONObject abuse = data.getJSONObject("abuse");
//            String ipBelong = abuse.getString("address");
//            requestRecord.setIpBelong(ipBelong);
//            requestRecordService.insert(requestRecord);
//        } else {
//            // 如果没有获取到滥用信息，则将 IP 归属地设置为“暂无”
//            requestRecord.setIpBelong("暂无");
//            requestRecordService.insert(requestRecord);
//        }

        // 从 Redis 中获取原始链接
        String redirectUrl = redisClientUtil.get(key);
        redirectUrl = UrlUtil.subKey(redirectUrl);
        // 创建并重定向到原始链接
        RedirectView redirectView = new RedirectView();
        redirectView.setUrl(redirectUrl);
        return redirectView;
    }


    /**
     * 获取短链接列表
     *
     * @param query 查询条件对象
     * @return ApiResponse<PageResult < UrlMap>> 包含短链接列表结果的响应对象
     */
    @Override
    public ApiResponse<PageResult<UrlMap>> getShortUrlList(ShortUrlQuery query) {
        // 获取页码
        Integer pageNo = query.getPageNo();
        // 获取每页显示的条数
        Integer pageSize = query.getPageSize();
        // 调用服务层方法获取分页结果
        PageResult<UrlMap> list = urlMapService.list(query.getUserId(), query.getRemark(), pageNo, pageSize);
        // 获取分页数据
        List<UrlMap> data = list.getData();
        // 遍历分页数据
        data.forEach(item -> {
            // 为每个短链接添加前缀
            item.setShortUrl(shortUrl + item.getShortUrl());
            // 统计每个短链接的点击次数
            Integer count = requestRecordService.countByUrlId(item.getId());
            // 设置短链接的点击次数
            item.setCount(count);
        });
        // 返回包含分页结果的响应
        return ApiResponse.ok(list);
    }

    @Override
    public ApiResponse<List<UrlMap>> batchCreateShortUrl(List<CreateShortUrlVo> createShortUrlVoList) {
        List<UrlMap> urlList = Lists.newArrayList();
        for (CreateShortUrlVo createShortUrlVo : createShortUrlVoList) {
            try {
                ApiResponse<UrlMap> response = createShortUrl(createShortUrlVo);
                urlList.add(response.getData());
            } catch (Exception e) {
                UrlMap urlMap = new UrlMap();
                // 设置短链接
                urlMap.setShortUrl(e.getMessage());
                // 设置原始链接
                urlMap.setLongUrl(createShortUrlVo.getUrl());
                urlList.add(urlMap);
            }
        }
        return ApiResponse.ok(urlList);
    }


    /**
 * 检查给定的 base62 编码是否已经存在于数据库中。
 * 如果存在，则生成一个新的 base62 编码，以避免冲突。
 *
 * @param url       原始 URL
 * @param base62    要检查的 base62 编码
 * @return 如果 base62 编码已经存在，则返回一个新的 base62 编码；否则，返回原始的 base62 编码
 */
private String checkBase62(String url, String base62) {
    // 使用布隆过滤器检查 base62 编码是否存在
    Boolean bfexists = bloomFilterUtil.bfexists(base62, url);
    // 初始化新的 base62 编码为原始的 base62 编码
    String newBase62 = base62;
    // 初始化计数器
    int count = 0;
    // 如果布隆过滤器返回存在
    while (bfexists) {
        // 从数据库中获取与 base62 编码对应的原始 URL
        String longUrl = urlMapService.getLongUrlByShortUrl(base62);
        // 如果从数据库中获取到了原始 URL（即 base62 编码已经存在于数据库中）
        if (StringUtils.isNoneBlank(longUrl)) {
            // 在原始的 base62 编码后面添加一个随机字符，生成一个新的 base62 编码
            newBase62 = base62 + RandomStringUtils.random(1);
            // 增加计数器
            count++;
            // 如果尝试次数超过了限制
            if (count > 10) {
                // 返回错误信息
                throw new RuntimeException("无法生成新的 base62 编码");
            }
            // 递归调用 checkBase62 方法，检查新生成的 base62 编码是否已经存在
            bfexists = bloomFilterUtil.bfexists(newBase62, url);
        } else {
            // 如果数据库中没有找到对应的原始 URL，则认为 base62 编码不存在
            bfexists = false;
        }
    }
    // 返回最终的 base62 编码
    return newBase62;
}



}
